<?php
//========================================================================
// Author:  Pascal KISSIAN
// Resume:  http://pascal.kissian.net
//
// Copyright (c) 2015-2019 Pascal KISSIAN
//
// Published under the MIT License
//          Consider it as a proof of concept!
//          No warranty of any kind.
//          Use and abuse at your own risks.
//========================================================================

use PhpParser\Error;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\Goto_;
use PhpParser\Node\Stmt\Label;
use PhpParser\Node\Stmt\Use_;


function reverse_obfuscate($filename)                   // takes a file_path as input, returns the corresponding obfuscated code as a string
{
    global $conf;
    global $parser,$traverser,$prettyPrinter;
    global $debug_mode;//全局变量


    $src_filename = $filename;//源文件或者目录
    $tmp_filename = $first_line = '';//临时文件
    $t_source = file($filename);//源文件？
    if (substr($t_source[0],0,2)=='#!')//开头字母
    {
        $first_line = array_shift($t_source);//第一行
        $tmp_filename = tempnam(sys_get_temp_dir(), 'po-');//临时文件
        file_put_contents($tmp_filename,implode(PHP_EOL,$t_source));//写入临时文件
        $filename = $tmp_filename; // override
    }



    try
    {
        $source = php_strip_whitespace($filename);//去除空格


        fprintf(STDERR,"Obfuscating %s%s",$src_filename,PHP_EOL); //混淆文件名
        //var_dump( token_get_all($source));    exit;
        if ($source==='')
        {
            if ($conf->allow_and_overwrite_empty_files) return $source;//文件名没有指定
            throw new Exception("Error obfuscating [$src_filename]: php_strip_whitespace returned an empty string!");
        }
        try
        {
            $stmts  = $parser->parse($source);  // PHP-Parser returns the syntax tree  获得ast


        }
        catch (PhpParser\Error $e)                              // if an error occurs, then redo it without php_strip_whitespace, in order to display the right line number with error!
        {
            $source = file_get_contents($filename);
            $stmts  = $parser->parse($source);
        }
        if ($debug_mode===2)                                    //  == 2 is true when debug_mode is true!
        {
            $source = file_get_contents($filename);
            $stmts  = $parser->parse($source);
        }
        if ($debug_mode) var_dump($stmts);//调试模式 输出ast



        $stmts  = $traverser->traverse($stmts);                 //  Use PHP-Parser function to traverse the syntax tree and obfuscate names

        /*

              if ($conf->shuffle_stmts && (count($stmts)>2) )//超过两个语句才执行
              {
                  $last_inst  = array_pop($stmts);//第一条语句？
                  $last_use_stmt_pos = -1;
                  foreach($stmts as $i => $stmt)                      // if a use statement exists, do not shuffle before the last use statement  第一个user和最后一个use之间不用混淆
                  {                                                   //TODO: enhancement: keep all use statements at their position, and shuffle all sub-parts
                      if ( $stmt instanceof PhpParser\Node\Stmt\Use_ ) $last_use_stmt_pos = $i;
                  }

                  if ($last_use_stmt_pos<0)   { $stmts_to_shuffle = $stmts;                                   $stmts = array();                                       }
                  else                        { $stmts_to_shuffle = array_slice($stmts,$last_use_stmt_pos+1); $stmts = array_slice($stmts,0,$last_use_stmt_pos+1);    }

                  $stmts      = array_merge($stmts,shuffle_statements($stmts_to_shuffle));//先混淆 除了use之外的，
                  $stmts[]    = $last_inst;//第一条语句？
              }
              // if ($debug_mode) var_dump($stmts);
               */

        $code   = trim($prettyPrinter->prettyPrintFile($stmts));            //  Use PHP-Parser function to output the obfuscated source, taking the modified obfuscated syntax tree as input  打印输出

        if (isset($conf->strip_indentation) && $conf->strip_indentation)    // self-explanatory
        {
            $code = remove_whitespaces($code);//去掉空格
        }
        $endcode = substr($code,6);//去掉前六个字符？<?php

        $code  = '<?php'.PHP_EOL;
        $code .= $conf->get_comment();                                          // comment obfuscated source
        if (isset($conf->extract_comment_from_line) && isset($conf->extract_comment_to_line) )//混淆注释
        {
            $t_source = file($filename);
            for($i=$conf->extract_comment_from_line-1;$i<$conf->extract_comment_to_line;++$i) $code .= $t_source[$i];
        }
        if (isset($conf->user_comment))
        {
            $code .= '/*'.PHP_EOL.$conf->user_comment.PHP_EOL.'*/'.PHP_EOL;
        }
        $code .= $endcode;

        if (($tmp_filename!='') && ($first_line!=''))
        {
            $code = $first_line.$code;
            unlink($tmp_filename);
        }

        return trim($code);
    }
    catch (Exception $e)
    {
        fprintf(STDERR,"Obfuscator Parse Error [%s]:%s\t%s%s", $filename,PHP_EOL, $e->getMessage(),PHP_EOL);
        return null;
    }
}

function obfuscate($filename)                   // takes a file_path as input, returns the corresponding obfuscated code as a string
{
    global $conf;
    global $parser,$traverser,$prettyPrinter;
    global $debug_mode;//全局变量
    
    $src_filename = $filename;//源文件或者目录
    $tmp_filename = $first_line = '';//临时文件
    $t_source = file($filename);//源文件？
    if (substr($t_source[0],0,2)=='#!')//开头字母
    {
        $first_line = array_shift($t_source);//第一行
        $tmp_filename = tempnam(sys_get_temp_dir(), 'po-');//临时文件
        file_put_contents($tmp_filename,implode(PHP_EOL,$t_source));//写入临时文件
        $filename = $tmp_filename; // override
    }
    
    try
    {
        $source = php_strip_whitespace($filename);//去除空格
        fprintf(STDERR,"Obfuscating %s%s",$src_filename,PHP_EOL); //混淆文件名
        //var_dump( token_get_all($source));    exit;
        if ($source==='')
        {
            if ($conf->allow_and_overwrite_empty_files) return $source;//文件名没有指定
            throw new Exception("Error obfuscating [$src_filename]: php_strip_whitespace returned an empty string!");
        }
        try
        {
            $stmts  = $parser->parse($source);  // PHP-Parser returns the syntax tree  获得ast
        }
        catch (PhpParser\Error $e)                              // if an error occurs, then redo it without php_strip_whitespace, in order to display the right line number with error!
        {
            $source = file_get_contents($filename);
            $stmts  = $parser->parse($source);
        }
        if ($debug_mode===2)                                    //  == 2 is true when debug_mode is true!
        {
            $source = file_get_contents($filename);
            $stmts  = $parser->parse($source);
        }
        if ($debug_mode) var_dump($stmts);//调试模式 输出ast

        $stmts  = $traverser->traverse($stmts);                 //  Use PHP-Parser function to traverse the syntax tree and obfuscate names
        if ($conf->shuffle_stmts && (count($stmts)>2) )//超过两个语句才执行
        {
            $last_inst  = array_pop($stmts);//第一条语句？
            $last_use_stmt_pos = -1;
            foreach($stmts as $i => $stmt)                      // if a use statement exists, do not shuffle before the last use statement  第一个user和最后一个use之间不用混淆
            {                                                   //TODO: enhancement: keep all use statements at their position, and shuffle all sub-parts
                if ( $stmt instanceof PhpParser\Node\Stmt\Use_ ) $last_use_stmt_pos = $i;
            }

            if ($last_use_stmt_pos<0)   { $stmts_to_shuffle = $stmts;                                   $stmts = array();                                       }
            else                        { $stmts_to_shuffle = array_slice($stmts,$last_use_stmt_pos+1); $stmts = array_slice($stmts,0,$last_use_stmt_pos+1);    }
            
            $stmts      = array_merge($stmts,shuffle_statements($stmts_to_shuffle));//先混淆 除了use之外的，
            $stmts[]    = $last_inst;//第一条语句？
        }
        // if ($debug_mode) var_dump($stmts);

        
        $code   = trim($prettyPrinter->prettyPrintFile($stmts));            //  Use PHP-Parser function to output the obfuscated source, taking the modified obfuscated syntax tree as input  打印输出

        if (isset($conf->strip_indentation) && $conf->strip_indentation)    // self-explanatory
        {
            $code = remove_whitespaces($code);//去掉空格
        }
        $endcode = substr($code,6);//去掉前六个字符？<?php

        $code  = '<?php'.PHP_EOL;
        $code .= $conf->get_comment();                                          // comment obfuscated source
        if (isset($conf->extract_comment_from_line) && isset($conf->extract_comment_to_line) )//混淆注释
        {
            $t_source = file($filename);
            for($i=$conf->extract_comment_from_line-1;$i<$conf->extract_comment_to_line;++$i) $code .= $t_source[$i];
        }
        if (isset($conf->user_comment))
        {
            $code .= '/*'.PHP_EOL.$conf->user_comment.PHP_EOL.'*/'.PHP_EOL;
        }
        $code .= $endcode;
        
        if (($tmp_filename!='') && ($first_line!=''))
        {
            $code = $first_line.$code;
            unlink($tmp_filename);
        }
        
        return trim($code);
    }
    catch (Exception $e)
    {
        fprintf(STDERR,"Obfuscator Parse Error [%s]:%s\t%s%s", $filename,PHP_EOL, $e->getMessage(),PHP_EOL);
        return null;
    }
}

function check_preload_file($filename)                       // self-explanatory
{
    for($ok=false;;)
    {
        if (!file_exists($filename)) return false;
        if (!is_readable($filename))
        {
            fprintf(STDERR,"Warning:[%s] is not readable!%s",$filename,PHP_EOL);
            return false;
        }
        $fp     = fopen($filename,"r"); if($fp===false) break;
        $line   = trim(fgets($fp));     if ($line!='<?php')                                     { fclose($fp); break; }
        $line   = trim(fgets($fp));     if ($line!='// YAK Pro - Php Obfuscator: Preload File') { fclose($fp); break; }
        fclose($fp);
        $ok     = true;
        break;
    }
    if (!$ok) fprintf(STDERR,"Warning:[%s] is not a valid yakpro-po preload file!%s\tCheck if file is php, and if magic line is present!%s",$filename,PHP_EOL,PHP_EOL);
    return $ok;
}

function check_config_file($filename)                       // self-explanatory
{
    for($ok=false;;)
    {
        if (!file_exists($filename)) return false;
        if (!is_readable($filename))
        {
            fprintf(STDERR,"Warning:[%s] is not readable!%s",$filename,PHP_EOL);
            return false;
        }
        $fp     = fopen($filename,"r"); if($fp===false) break;
        $line   = trim(fgets($fp));     if ($line!='<?php')                                     { fclose($fp); break; }
        $line   = trim(fgets($fp));     if ($line!='// YAK Pro - Php Obfuscator: Config File')  { fclose($fp); break; }
        fclose($fp);
        $ok     = true;
        break;
    }
    if (!$ok) fprintf(STDERR,"Warning:[%s] is not a valid yakpro-po config file!%s\tCheck if file is php, and if magic line is present!%s",$filename,PHP_EOL,PHP_EOL);
    return $ok;
}

function create_context_directories($target_directory)      // self-explanatory
{
    foreach( array("$target_directory/yakpro-po","$target_directory/yakpro-po/obfuscated","$target_directory/yakpro-po/context") as $dummy => $dir)
    {
        if (!file_exists($dir)) mkdir($dir,0777,true);
        if (!file_exists($dir))
        {
            fprintf(STDERR,"Error:\tCannot create directory [%s]%s",$dir,PHP_EOL);
            exit(-1);
        }
    }
    $target_directory = realpath($target_directory);
    if (!file_exists("$target_directory/yakpro-po/.yakpro-po-directory")) touch("$target_directory/yakpro-po/.yakpro-po-directory");
}


function remove_directory($path)                            // self-explanatory
{
    if ($dp = opendir($path))
    {
        while (($entry = readdir($dp)) !==  false )
        {
            if ($entry ==  ".") continue;
            if ($entry == "..") continue;

                 if (is_link("$path/$entry"))   unlink("$path/$entry" );            // remove symbolic links first, to not dereference...
            else if (is_dir ("$path/$entry"))   remove_directory("$path/$entry");
            else                                unlink("$path/$entry" );
        }
        closedir($dp);
        rmdir($path);
    }
}

function confirm($str)                                  // self-explanatory not yet used ... rfu
{
    global $conf;
    if (!$conf->confirm) return true;
    for(;;)
    {
        fprintf(STDERR,"%s [y/n] : ",$str);
        $r = strtolower(trim(fgets(STDIN)));
        if ($r=='y')    return true;
        if ($r=='n')    return false;
    }
}

function reverse_obfuscate_directory($source_dir,$target_dir,$keep_mode=false)   // self-explanatory recursive obfuscation
{
    global $conf;



    if (!$dp = opendir($source_dir))//混淆目录
    {
        fprintf(STDERR,"Error:\t [%s] directory does not exists!%s",$source_dir,PHP_EOL);
        exit(-1);
    }
    $t_dir  = array();
    $t_file = array();
    while (($entry = readdir($dp)) !== false)
    {
        if ($entry == "." || $entry == "..")    continue;

        $new_keep_mode = $keep_mode;

        $source_path = "$source_dir/$entry";    $source_stat = @lstat($source_path);
        $target_path = "$target_dir/$entry";    $target_stat = @lstat($target_path);
        if ($source_stat===false)
        {
            fprintf(STDERR,"Error:\t cannot stat [%s] !%s",$source_path,PHP_EOL);
            exit(-1);
        }

        if (isset($conf->t_skip) && is_array($conf->t_skip) && in_array($source_path,$conf->t_skip))    continue;

        if (is_link($source_path))
        {
            if ( ($target_stat!==false) && is_link($target_path) && ($source_stat['mtime']<=$target_stat['mtime']) )    continue;
            if (  $target_stat!==false  )
            {
                if (is_dir($target_path))   remove_directory($target_path);
                else
                {
                    if (unlink($target_path)===false)
                    {
                        fprintf(STDERR,"Error:\t cannot unlink [%s] !%s",$target_path,PHP_EOL);
                        exit(-1);
                    }
                }
            }
            @symlink(readlink($source_path), $target_path);     // Do not warn on non existing symbolinc link target!
            if (strtolower(PHP_OS)=='linux')    $x = `touch '$target_path' --no-dereference --reference='$source_path' `;
            continue;
        }
        if (is_dir($source_path))
        {
            if ($target_stat!==false)
            {
                if (!is_dir($target_path))
                {
                    if (unlink($target_path)===false)
                    {
                        fprintf(STDERR,"Error:\t cannot unlink [%s] !%s",$target_path,PHP_EOL);
                        exit(-1);
                    }
                }
            }
            if (!file_exists($target_path)) mkdir($target_path,0777, true);
            if (isset($conf->t_keep) && is_array($conf->t_keep) && in_array($source_path,$conf->t_keep))    $new_keep_mode = true;
            reverse_obfuscate_directory($source_path,$target_path,$new_keep_mode);
            continue;
        }
        if(is_file($source_path))
        {
            if ( ($target_stat!==false) && is_dir($target_path) )                               remove_directory($target_path);
            if ( ($target_stat!==false) && ($source_stat['mtime']<=$target_stat['mtime']) )     continue;                       // do not process if source timestamp is not greater than target

            $extension  = pathinfo($source_path,PATHINFO_EXTENSION);

            $keep = $keep_mode;
            if (isset($conf->t_keep) && is_array($conf->t_keep) && in_array($source_path,$conf->t_keep))    $keep = true;
            if (!in_array($extension,$conf->t_obfuscate_php_extension) )                                    $keep = true;

            if ($keep)
            {
                file_put_contents($target_path,file_get_contents($source_path));
            }
            else
            {

                $obfuscated_str =  reverse_obfuscate($source_path);
                if ($obfuscated_str===null)
                {
                    if (isset($conf->abort_on_error))
                    {
                        fprintf(STDERR, "Aborting...%s",PHP_EOL);
                        exit;
                    }
                }
                file_put_contents($target_path,$obfuscated_str.PHP_EOL);
            }
            touch($target_path,$source_stat['mtime']);
            chmod($target_path,$source_stat['mode']);
            chgrp($target_path,$source_stat['gid']);
            chown($target_path,$source_stat['uid']);
            continue;
        }
    }
    closedir($dp);
}


function obfuscate_directory($source_dir,$target_dir,$keep_mode=false)   // self-explanatory recursive obfuscation
{
    global $conf;

    if (!$dp = opendir($source_dir))//混淆目录
    {
        fprintf(STDERR,"Error:\t [%s] directory does not exists!%s",$source_dir,PHP_EOL);
        exit(-1);
    }
    $t_dir  = array();
    $t_file = array();
    while (($entry = readdir($dp)) !== false)
    {
        if ($entry == "." || $entry == "..")    continue;

        $new_keep_mode = $keep_mode;

        $source_path = "$source_dir/$entry";    $source_stat = @lstat($source_path);
        $target_path = "$target_dir/$entry";    $target_stat = @lstat($target_path);
        if ($source_stat===false)
        {
            fprintf(STDERR,"Error:\t cannot stat [%s] !%s",$source_path,PHP_EOL);
            exit(-1);
        }

        if (isset($conf->t_skip) && is_array($conf->t_skip) && in_array($source_path,$conf->t_skip))    continue;

        if (is_link($source_path))
        {
            if ( ($target_stat!==false) && is_link($target_path) && ($source_stat['mtime']<=$target_stat['mtime']) )    continue;
            if (  $target_stat!==false  )
            {
                if (is_dir($target_path))   remove_directory($target_path);
                else
                {
                    if (unlink($target_path)===false)
                    {
                        fprintf(STDERR,"Error:\t cannot unlink [%s] !%s",$target_path,PHP_EOL);
                        exit(-1);
                    }
                }
            }
            @symlink(readlink($source_path), $target_path);     // Do not warn on non existing symbolinc link target!
            if (strtolower(PHP_OS)=='linux')    $x = `touch '$target_path' --no-dereference --reference='$source_path' `;
            continue;
        }
        if (is_dir($source_path))
        {
            if ($target_stat!==false)
            {
                if (!is_dir($target_path))
                {
                    if (unlink($target_path)===false)
                    {
                        fprintf(STDERR,"Error:\t cannot unlink [%s] !%s",$target_path,PHP_EOL);
                        exit(-1);
                    }
                }
            }
            if (!file_exists($target_path)) mkdir($target_path,0777, true);
            if (isset($conf->t_keep) && is_array($conf->t_keep) && in_array($source_path,$conf->t_keep))    $new_keep_mode = true;
            obfuscate_directory($source_path,$target_path,$new_keep_mode);
            continue;
        }
        if(is_file($source_path))
        {
            if ( ($target_stat!==false) && is_dir($target_path) )                               remove_directory($target_path);
            if ( ($target_stat!==false) && ($source_stat['mtime']<=$target_stat['mtime']) )     continue;                       // do not process if source timestamp is not greater than target

            $extension  = pathinfo($source_path,PATHINFO_EXTENSION);

            $keep = $keep_mode;
            if (isset($conf->t_keep) && is_array($conf->t_keep) && in_array($source_path,$conf->t_keep))    $keep = true;
            if (!in_array($extension,$conf->t_obfuscate_php_extension) )                                    $keep = true;

            if ($keep)
            {
                file_put_contents($target_path,file_get_contents($source_path));
            }
            else
            {
                $obfuscated_str =  obfuscate($source_path);
                if ($obfuscated_str===null)
                {
                    if (isset($conf->abort_on_error))
                    {
                        fprintf(STDERR, "Aborting...%s",PHP_EOL);
                        exit;
                    }
                }
                file_put_contents($target_path,$obfuscated_str.PHP_EOL);
            }
            touch($target_path,$source_stat['mtime']);
            chmod($target_path,$source_stat['mode']);
            chgrp($target_path,$source_stat['gid']);
            chown($target_path,$source_stat['uid']);
            continue;
        }
    }
    closedir($dp);
}

function shuffle_get_chunk_size(&$stmts)
{
    global $conf;

    $n = count($stmts);
    switch($conf->shuffle_stmts_chunk_mode)
    {
        case 'ratio':
            $chunk_size = sprintf("%d",$n/$conf->shuffle_stmts_chunk_ratio)+0;
            if ($chunk_size<$conf->shuffle_stmts_min_chunk_size) $chunk_size = $conf->shuffle_stmts_min_chunk_size;
            break;
        case 'fixed':
            $chunk_size = $conf->shuffle_stmts_min_chunk_size;
            break;
        default:
            $chunk_size =  1;       // should never occur!
    }
    return $chunk_size;
}






//反混淆处理 节点
function reverse_shuffle_statements($stmts){
    global $conf;
    global $t_scrambler;

    if (!$conf->shuffle_stmts)          return $stmts;//不做shuffle处理

    $chunk_size = shuffle_get_chunk_size($stmts);
    if ($chunk_size<=0)                 return $stmts; // should never occur!

    $n = count($stmts);//n行代码
    if ($n<(2*$chunk_size))             return $stmts;//不足一行，不打乱


    $label_name_next       = '';//下一个label名称
    $label_index =0;//本次循环起点
    $goto_index=0;//本次循环终点
    $t                      = array();//修正结果
    //label->t_chunk->goto

    for($i=$label_index;$i<$n;)//本身就是乱序 这样挨个循环没有意义  最多到n
    {
        $t_chunk              = $stmts[$i];


            if($n<3){//不足三个语句，没有混淆
                $t=$stmts;
                break;
            }

            $found =false;
            for($j=$i;$j<$n;++$j)
            {
                //找到goto
                if($stmts[$j]  instanceof PhpParser\Node\Stmt\Goto_){
                    $goto_index=$j;
                    $found =true;
                    break;
                }
            }


            //处理合并
            if($found==false){
                //没有goto 直接拼接  直接到代码块结尾
               // var_dump('没有goto 直接拼接');
                for($tmp=$i+1;$tmp<$n;$tmp++){
                    $t[]  =$stmts[$tmp];
                }
                break;
            }else{//找到goto 过滤label goto
                if($i==$j){
                    //一条语句 label和goto重合
                    $t[]  =$stmts[$i];

                }else if($i+2==$j) {

                    for($tmp=$i;$tmp<=$j;$tmp++){

                        $t[]  =$stmts[$tmp];
                    }


                }else{

                }
            }



            //下一个要跳转的标签
            $goto =$stmts[$j];
            $label_name_next=$goto->name;//下一个要查找的标签


            $found =false;
            for($ts=0;$ts<$n;++$ts)//从头查找
            {
                if($stmts[$ts]  instanceof PhpParser\Node\Stmt\Label && $stmts[$ts]->name==$label_name_next){
                    $i=$ts;
                    $found=true;

                    break;
                }
            }


            if($i==$n-1){
                //最后一个label 退出
                var_dump('最后一个label 退出');
                break;
            }


            if(!$found){//没有找到goto跳转标签
                var_dump('没有找到goto跳转标签');
                break;

            }


    }



    foreach($t as $dummy => $stmt)
    {
        foreach($stmt as $dummy => $inst) $stmts[] = $inst;
    }
    return $stmts;//调整过顺序的代码块
}

function shuffle_statements($stmts)//goto 混淆算法
{
    global $conf;
    global $t_scrambler;

    if (!$conf->shuffle_stmts)          return $stmts;
    
    $chunk_size = shuffle_get_chunk_size($stmts);
    if ($chunk_size<=0)                 return $stmts; // should never occur!
    
    $n = count($stmts);//n行代码
    if ($n<(2*$chunk_size))             return $stmts;//不足一行，不打乱
    
    $scrambler              = $t_scrambler['label'];//这个全局变量？
    $label_name_prev        = $scrambler->scramble($scrambler->generate_label_name());//生成label
    $first_goto             = new PhpParser\Node\Stmt\Goto_($label_name_prev);//生成goto

    $t                      = array();
    for($i=0;$i<$n;++$i)
    {
        $t_chunk[]              = $stmts[$i];
        $t_chunk                = array();
        if (count($t_chunk)>=$chunk_size)
        {
            $label              = array(new PhpParser\Node\Stmt\Label($label_name_prev));
            $label_name         = $scrambler->scramble($scrambler->generate_label_name());
            $goto               = array(new PhpParser\Node\Stmt\Goto_($label_name));
            $t[]                = array_merge($label,$t_chunk,$goto);//合并代码数
            $label_name_prev    = $label_name;//上一个标签
            $t_chunk            = array();
        }
    }
    if (count($t_chunk)>0)
    {
        $label              = array(new PhpParser\Node\Stmt\Label($label_name_prev));
        $label_name         = $scrambler->scramble($scrambler->generate_label_name());
        $goto               = array(new PhpParser\Node\Stmt\Goto_($label_name));
        $t[]                = array_merge($label,$t_chunk,$goto);
        $label_name_prev    = $label_name;
        $t_chunk            = array();
    }
    
    $last_label             = new PhpParser\Node\Stmt\Label($label_name);
    shuffle($t);
    $stmts = array();
    $stmts[] = $first_goto;
    foreach($t as $dummy => $stmt)
    {
        foreach($stmt as $dummy => $inst) $stmts[] = $inst;
    }
    $stmts[] = $last_label;
    return $stmts;
}

function remove_whitespaces($str)
{
    $tmp_filename = @tempnam(sys_get_temp_dir(),'po-');
    file_put_contents($tmp_filename,$str);
    $str = php_strip_whitespace($tmp_filename);  // can remove more whitespaces
    unlink($tmp_filename);
    return $str;
}

?>
